package model;
import java.util.HashMap;
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.awt.Color;

/**
 * A simple predator-prey simulator, based on a rectangular field
 * containing rabbits, foxes, wolves, cars, hunters.
 * @author Arjan Frans, Alwin Rombout, Jelte Verbree, Brendan Kanters
 * @version 1.0
 */
public class Simulator extends AbstractModel implements Runnable
{
    // Constants representing configuration information for the simulation.
    // The default width for the grid.
    private static final int DEFAULT_WIDTH = 50;
    // The default depth of the grid.
    private static final int DEFAULT_DEPTH = 50;
    // The probability that a fox will be created in any given grid position.
    private int foxPrb;
    // The probability that a rabbit will be created in any given grid position.
    private int rabbitPrb;    
    // The probability that a wolf will be created in any given grid position.
    private int wolfPrb;    

    private int hunterPrb;
    
    private int grassPrb;
    
    private int roadWidth;
    
    private int carsY;
    
    private int fullness;
    // List of animals in the field.
    private ArrayList<Actor> actors;
    // The current state of the field.
    private Field field;
    // The current step of the simulation.
    private int step;
    
    private int speed;
    
    private boolean road;

    // Turn on or off random population
    private boolean randPopulation;  
    private FieldStats stats;
	private int numOfSteps;
	private boolean run;
	private boolean nuke = false;
	private boolean ready;

    
    /**
     * Construct a simulation field with default size.
     */
    public Simulator()
    {
        this(DEFAULT_DEPTH, DEFAULT_WIDTH);
        foxPrb = 10;
        rabbitPrb = 30;
        wolfPrb = 6;
        hunterPrb = 1;
        grassPrb = 15;
        roadWidth = 4;
        carsY = 4;
        fullness=4;
        road = false;
    }
   
    
    /**
     * Create a simulation field with the given size.
     * @param depth Depth of the field. Must be greater than zero.
     * @param width Width of the field. Must be greater than zero.
     */
    public Simulator(int depth, int width)
    { 	
    	randPopulation = true;
        if(width <= 0 || depth <= 0) {
            System.out.println("The dimensions must be greater than zero.");
            System.out.println("Using default values.");
            depth = DEFAULT_DEPTH;
            width = DEFAULT_WIDTH;
        }
        speed = 150;
        actors = new ArrayList<Actor>();
        field = new Field(depth, width);
        stats = new FieldStats();
    }
    
    /**
     * Constructor to add custom placed actors, used for testing
     * @param ArrayList<Actor> newActors
     * @param depth
     * @param width
     */
    public Simulator(ArrayList<Actor> newActors, int depth, int width) 
    { 	
    	randPopulation = false;
    	if(width <= 0 || depth <= 0) {
            System.out.println("The dimensions must be greater than zero.");
            System.out.println("Using default values.");
            depth = DEFAULT_DEPTH;
            width = DEFAULT_WIDTH;
        }
        
        actors = new ArrayList<Actor>();
        field = new Field(depth, width);
        stats = new FieldStats();
	    customPopulate(newActors);
    }
    
    /**
     * count the actors in the simulation
     */
	public void getActorCounts()
    {
    	stats.reset();
        for(int row = 0; row < field.getDepth(); row++) {
            for(int col = 0; col < field.getWidth(); col++) {
                Object actor = field.getObjectAt(row, col);
                if(actor != null && !(actor instanceof Grass)) {
                	 stats.incrementCount(actor.getClass()); 
                }
            }
        }
        stats.countFinished();
    }
   
	/**
	 * 
	 * @return the number of steps passed
	 */
    public String getStep()
    {
    	return Integer.toString(step);
    }
    
    /**
     * 
     * @return the fieldstats
     */
    public FieldStats getStats()
    {
    	return stats;
    }    
        
    /**
     * 
     * @return the field
     */
    public Field getField()
    {
    	return field;
    }
    
    /**
     * set the field
     * @param field
     */
    public void setField(Field field)
    {
    	this.field = field;
    }
   
    
    /**
     * Run the simulation from its current state for a reasonably long period,
     * e.g. 500 steps.
     */
    public void runLongSimulation()
    {
        simulate(500);
    }
    
    /**
     * Run the simulation from its current state for the given number of steps.
     * Stop before the given number of steps if it ceases to be viable.
     * @param numSteps The number of steps to run for.
     */
    public void simulate(int numSteps)
    {
        for(int step = 1; step <= numSteps; step++) {
            simulateOneStep();
        }
    }
    
    /**
     * Run the simulation without the viable boolean
     * @param numSteps
     */
    public void simulateNoStop(int numSteps)
    {
    	for(int step = 1; step <= numSteps; step++) {
            simulateOneStep();
        }
    }
    
    /**
     * Run the simulation from its current state for a single step.
     * Iterate over the whole field updating the state of each
     * object
     */
    public void simulateOneStep()
    {
        step++;    
        // Provide space for newborn animals.
        List<Actor> newActors = new ArrayList<Actor>();        
        for(Iterator<Actor> it = actors.iterator(); it.hasNext(); ) {
            Actor actor = it.next();
            actor.act(newActors);
            if(!actor.isAlive()) {
                it.remove();
            }
        }
        
        actors.addAll(newActors);
        getActorCounts();
        notifyViews();     
    }
        
    /**
     * Reset the simulation to a starting position.
     */
    public void reset()
    {
        nuke = false;
    	stats.clearHistory();
        step = 0; 
        actors.clear();
        populate();
        getActorCounts();
        clearViews();
        notifyViews();
    }
    
    /**
     * set randPopulation    
     * @param randPopulation
     */
    public void setRandPopulation(boolean randPopulation)
    {
    	this.randPopulation= randPopulation; 
    }
    
    /**
     * Randomly populate the field with the objects.
     */
    private void populate()
    {
    	field.clear();

    	if(randPopulation == true) {
	        Random rand = Randomizer.getRandom();	
	        int totalPrb = fullness*(foxPrb + rabbitPrb + wolfPrb + hunterPrb + grassPrb);
	        for(int row = 0; row < field.getDepth(); row++) {
	            for(int col = 0; col < field.getWidth(); col++) {
	            	Location location = new Location(row, col);
	            	if(rand.nextInt(totalPrb) <= grassPrb) {
	            		if(!field.getRoadLocation(location.getRow(), location.getCol())) {
		            		Grass grass = new Grass(field, location);
		            		actors.add(grass);
	            		}
	            	}
	            	else if(rand.nextInt(totalPrb) <= hunterPrb) {
	            		
	            		Hunter hunter = new Hunter(field, location);
	            		actors.add(hunter);
	            	}
	            	else if(rand.nextInt(totalPrb) <= wolfPrb) {
	            		Wolf wolf = new Wolf(true, field, location);
	            		actors.add(wolf);
	            	}
	            	else if(rand.nextInt(totalPrb) <= foxPrb) {
	                    Fox fox = new Fox(true, field, location);
	                    actors.add(fox);
	                }
	                else if(rand.nextInt(totalPrb) <= rabbitPrb) {
	                    Rabbit rabbit = new Rabbit(true, field, location);
	                    actors.add(rabbit);
	                }
	            	
	                // else leave the location empty.
	            }
	        }
    	}
    	
    	if(road == true) {
	    	//make the road
	    	int col = field.getWidth()/2;
	    	for(int x = col-(roadWidth/2); x <= col+(roadWidth/2);x++) {
		    	for(int row = 0;row < field.getDepth();row++)
		    	{
		    		Location location = new Location(row, x);
		    		location.setRoad(true);
		    		field.addRoadLocation(location);
		    	}
	    	}
	    	
	    	//place cars
	    	int currentDirection = -1;
	    	for(int x=0;x<roadWidth;x++) {
	    		for(int y=0;y < carsY;y++)
	    		{
	    			int randRow = new Random().nextInt(field.getDepth()-1);
		    		Location startLocation = new Location(randRow,(col-(roadWidth/2))+x);
		    		Car car = new Car(field, startLocation);
		    		car.setDirection(currentDirection);
		    		//Change direction
		    		if(x >= (roadWidth/2)) currentDirection = +1;
		    		actors.add(car);
	    		}
	    	}
    	}
    	else if(road == false)
    	{
    		field.clearRoad();
    	}
    }
    
    /**
     * populate the field with objects
     * @param newActors
     */
    public void customPopulate(ArrayList<Actor> newActors)
    { 	
    	Iterator<Actor> it = newActors.iterator();
    	while(it.hasNext()) {
    		Actor actor = it.next();
    		Location loc = actor.getLocation();
    		if(actor instanceof Fox) {   			
    			Fox fox = new Fox(false, field, loc);
    			fox.setAge(((Animal) actor).getAge());
    			actors.add(fox);
    		}
    		if(actor instanceof Rabbit) {
    			Rabbit rabbit = new Rabbit(true, field, loc);
    			rabbit.setAge(((Animal) actor).getAge());
    			boolean ziek = false;
    			if(((Rabbit) actor).getZiekteGen() == true)
    			{
    				ziek = true;
    			}
    			//rabbit.setZiekteGen(ziek);
    			actors.add(rabbit);
    		}
    		if(actor instanceof Wolf) {
    			Wolf wolf = new Wolf(true, field, loc);
    			wolf.setAge(((Animal) actor).getAge());
    			actors.add(wolf);
    		}
    		if(actor instanceof Hunter) {
    			Hunter hunter = new Hunter(field, loc);
    			actors.add(hunter);
    		}
    	}
    }
    
    /**
     * 
     * @return colors
     */
	public HashMap<Class, Color> getColors()
    {
    	HashMap<Class, Color> colors = new HashMap<Class, Color>();
    	for(int i=0;i < actors.size();i++)
    	{
    		Actor actor = actors.get(i);
    		colors.put(actor.getClass(), actor.getColor());
    	}
		return colors;
    	
    }
    
	/**
	 * 
	 * @return actors
	 */
    public ArrayList<Actor> getActors()
    {
    	return actors;
    }
    
    /**
     * run the simulation for a number of steps
     * @param numOfSteps
     */
    public void runSimulation(int numOfSteps)
    {
    	
    	this.numOfSteps = numOfSteps;
    	run = true;
    	new Thread(this).start();
    }
    
    /**
     * 
     * @param numOfSteps
     */
    public void runBombThread(int numOfSteps)
    {
    	
    	this.numOfSteps = numOfSteps;
    	run = true;
    }
    
    /**
     * stop the simulation
     */
    public void stopSimulation()
    {
    	run = false;
    }
    
    /**
     * set probabilities for all objects
     * @param foxPrb
     * @param rabbitPrb
     * @param wolfPrb
     * @param hunterPrb
     * @param grassPrb
     */
    public void setProbabilities(int foxPrb, int rabbitPrb, int wolfPrb, int hunterPrb, int grassPrb)
    {
    	this.foxPrb = foxPrb;
    	this.rabbitPrb = rabbitPrb;
    	this.wolfPrb = wolfPrb;
    	this.hunterPrb = hunterPrb;
    	this.grassPrb = grassPrb;
    }
    
    /**
     * set road settings
     * @param roadWidth
     * @param carsY
     */
    public void setRoadSettings(int roadWidth, int carsY)
    {
    	this.roadWidth = roadWidth;
    	this.carsY = carsY;
    }
    
    /**
     * set fullness
     * @param fullness
     */
    public void setFullness(int fullness)
    {
    	this.fullness = fullness;
    }
    
    /**
     * set ready
     */
    public void setReady()
    {
    	ready = true;
    }
    
    /**
     * 
     * @return boolean ready
     */
    public boolean isReady()
    {
    	return ready;
    }

    /**
     * run the simulation
     */
    @Override
	public void run() {
		for(int i=0;i<numOfSteps && run;i++) {
			simulateOneStep();
			try {
				Thread.sleep(speed);
				stats.addHistory(stats.getCounters());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		run=false;
		
	}
    
    /**
     * set speed of the simulation
     * @param speed
     */
    public void setSpeed(int speed)
    {
    	this.speed = speed;
    }

    /**
     * use the nuke
     * @param nuke
     */
	public void nuke(boolean nuke) {
		new Sound().playSound();
		this.nuke = nuke;
		stopSimulation();
		notifyViews();

	}

	/**
	 * @return the nuke
	 */
	public boolean drawBomb() {	
		return nuke;
	}
	
	/**
	 * switch road on or off
	 * @param road
	 */
	public void setRoad(boolean road)
	{
		this.road = road;
	}

}

